Skip to content

feat: add display_map tool for point maps on OpenStreetMap#1206

Merged
Bl3f merged 4 commits into
getnao:mainfrom
tckaros:feat/display-map-tool
Jul 23, 2026
Merged

feat: add display_map tool for point maps on OpenStreetMap#1206
Bl3f merged 4 commits into
getnao:mainfrom
tckaros:feat/display-map-tool

Conversation

@tckaros

@tckaros tckaros commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What

A new display_map agent tool that plots geographic point data from execute_sql results on an interactive MapLibre GL map, using OpenFreeMap (OSM-based vector tiles) as the default basemap.

This is the proof of concept behind #1205 — it's fully working, but it's mainly here to make that discussion concrete. If maps are a direction you want, I fully expect remarks on the approach and I'm happy to rework it.

Capture d’écran 2026-07-16 à 22 53 20

How it works

  • Shared schema (apps/shared/src/tools/display-map.ts): query_id reference, lat/lng column keys, optional popup label_key/tooltip_keys, map_type enum ready for a future heatmap mode.
  • Shared geo logic (apps/shared/src/map-points.ts): one implementation used by both backend validation and frontend rendering — case-insensitive column resolution (exact match first), rejection of null/blank coordinates before Number() coercion (no fabricated (0,0) points), Web Mercator latitude limit, antimeridian-aware bounds.
  • Backend tool: resolves the query_id via getQueryResult (current run, then persisted chat history) and returns actionable errors (missing query, unresolvable column, no plottable rows) so the model is never told a map displayed when the UI can't render one. On success it reports plotted/dropped counts and warns about unresolvable popup keys or results over the render cap, so the model can self-correct its SQL.
  • Frontend: mirrors display_chart (same source-query resolution, now factored into a shared useSourceQuery hook used by both). MapLibre loads as a lazy chunk; popups are built with textContent only so query values can't inject HTML; rendering is capped at 5,000 points with a "showing first N" note; WebGL-unavailable clients get a graceful fallback instead of a crash.
  • Non-web surfaces: Slack/Teams/Telegram can't render an interactive map, so a completed display_map degrades to a "View interactive map in nao" link card. Automations, the MCP ask_nao sub-agent and WhatsApp exclude the tool entirely (WEB_CHAT_ONLY_TOOLS + a new getTools excludeBuiltinTools option), and the system prompt only advertises the tool when the run's tool set includes it.

Tests

  • apps/shared/tests/map-points.test.ts — coordinate validation, key resolution, wrapped bounds (16 tests)
  • apps/backend/tests/display-map-tool.test.ts — tool validation paths incl. history fallback (13 tests)
  • apps/backend/tests/agent-tools.test.ts + system-prompt gating tests

Known limitations / follow-ups

  • Basemap is a third-party service (tiles.openfreemap.org) and the VITE_MAP_STYLE_URL override is build-time only — see [feature] Map visualization for latitude/longitude query results #1205 for the configurability question.
  • Map colors and basemap style are snapshotted on mount (light/dark toggle needs a remount) — TODO in code.
  • No static PNG rendering for Slack/Teams/email yet (charts have one via generate-chart); WhatsApp link card TODO.
  • Polar data beyond ±85.05° is dropped (Web Mercator); globe projection would be the real answer.

🤖 Generated with Claude Code

Review in cubic

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🧹 Preview Removed

The preview deployment for this PR has been cleaned up.

@tckaros
tckaros force-pushed the feat/display-map-tool branch from 8cfda0a to 801f2e5 Compare July 16, 2026 21:21

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/backend/src/agents/tools/display-map.ts
Comment thread apps/backend/src/agents/tools/index.ts Outdated
@tckaros
tckaros force-pushed the feat/display-map-tool branch from 801f2e5 to 65a79d5 Compare July 16, 2026 21:37
@Bl3f

Bl3f commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Hello @tckaros I really really like this idea and this is something we should add in nao either with this PR or in another form so lets move it forward.

2 things to have in mind I think before the review:

  • we already have a PR opened that we can expect to merge this week to support "vibe-coded chart" ; which is a system of plugins so as admins you could define a set of viz that you want your users to be able to use (cf. feat: custom chart plugins ("vibe coded charts") #945) — so really customs maps could fall into this
  • tho, i think we should have by default a scatterplot map at least and a choropleth, for this the main question for me is: should we give the agent a display_map tool or should we consider maps like charts and include this in display_chart tool

Happy to hear your thoughts!

@socallmebertille socallmebertille left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks a lot for this awesome contribution ! 👍💯

On top of your work, I added for now :

  • the WhatsApp integration (just a link that redirects the user to see the map in the chat UI)
  • the dark mode theme handling (and align the design to the rest of the app)

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 36 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/backend/src/services/telegram.ts Outdated
Comment thread apps/backend/src/services/agent.ts Outdated
tckaros and others added 2 commits July 23, 2026 15:50
Adds a new display_map agent tool that renders geographic point data
from execute_sql results on an interactive MapLibre GL map, using
OpenFreeMap (OSM-based vector tiles) as the default basemap,
overridable via VITE_MAP_STYLE_URL.

- Shared zod schema with query_id reference, lat/lng column keys and
  optional popup label/tooltip keys; map_type enum ready for a future
  heatmap mode
- Point building/validation lives in @nao/shared (map-points.ts):
  column keys resolve exact-first then case-insensitively
  (resolveColumnName, with resolveDataKey moved to shared on top of
  it), rows with null or blank coordinates are rejected before
  Number() coercion so no fabricated (0,0) points are plotted,
  latitudes beyond the Web Mercator limit (85.051129°) are dropped
  instead of collapsing onto the map edge, and keys resolving to the
  same column are rejected instead of silently plotting lat=lng
- The tool resolves the query_id through getQueryResult (current run,
  then persisted chat history) and fails with an actionable error when
  the query is missing, a column does not resolve, or no row carries
  plottable coordinates; on success it reports plotted and dropped row
  counts so the model can notice partial data and fix its SQL
- Frontend component mirrors display_chart: resolves rows from the
  sibling execute_sql message part; MapLibre loads in a lazy chunk so
  the main bundle is unaffected; kept out of collapsed tool groups
  like charts; rendering is capped at 5000 points with a "showing
  first N" note
- fitBounds wraps across the antimeridian (computeMapBounds), so
  points at 179°E/179°W fit a 2° span instead of a world-scale view
- Popups are built with textContent only so query values cannot
  inject HTML
- Slack, Teams and Telegram cannot render an interactive map, so a
  completed display_map degrades to a "View interactive map in nao"
  link card; automations, the MCP ask_nao sub-agent and WhatsApp
  exclude the tool (shared WEB_CHAT_ONLY_TOOLS list, getTools
  excludeBuiltinTools option) and the system prompt only advertises
  the tool when the run's tool set includes it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Bl3f
Bl3f merged commit cf594c6 into getnao:main Jul 23, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants